// hdu5304
// 题意: 给你一个n(<=16)个点，m(>=n)条无向边的简单图，问删去m-n条边后，
//       还使得图联通，问有多少不同的删法。
//
// 题解：其实问题可以等价的转化为，给定n个点，m条边的图保留其中n条边，
//       使得图依旧联通，问有多少保留方法。      
//
//       我们可以想到，如果是n-1条边的联通图肯定是个树，那么对于n条边还要
//       保证联通就是一个树套环（环桃树）。
//       对于给定的一个图要求生成树个数可以用matrix tree定理解决。
//       那么我们可以枚举环上点(O(2^n))然后将环缩成一个点，这就是一个树了。
//       接下来问题就是对于一个确定环上的点集，如何计算环的个数。
//
//       这个可以用一个动态规划来完成。我们用dp[end][S]表示当前
//       结束点是end，经过的点集合是S的方案数。
//       我们先枚举环上最小标号点，然后对于每个合法的dp[i][S], 
//       我们枚举和i相邻且大于最小标号的且不在集合S里的点j，
//       dp[j][S+j] += dp[i][S]。如果j到最小标号点有边，说明这里有个环，
//       更新点集S+j的圆的个数，即：circle[S+j] += dp[j][S+j]。
//
//       这里要注意的是，这样算出来要除以2, 因为两个方向的环都算了一遍。
//       答案要取模，除法用逆元。
//
//       然后就简单了，对于每个圆上集合，把缩点后的图拿去算生成树个数，
//       乘起来累加就行。
//
// run: $exec < input
#include <iostream>
#include <algorithm>
#include <cstring>

int const mo = 998244353;
int const maxn = 16;
int const max_state = (1 << maxn) + 100;
bool map[maxn][maxn];
int circle[max_state];
int dp[maxn][max_state]; // dp[i][j] means the count of paths
						 // end with point i and pass thourgh points set j
int n, m;

void init()
{
	std::memset(map, 0, sizeof(map));
	std::memset(circle, 0, sizeof(circle));
}

void exgcd(int a, int b, int & x, int & y)
{
	if (b) exgcd(b, a % b, y, x), y -= a / b * x ;
	else x = 1, y = 0;
}

int inv(int a)
{
	int x, y, b = mo;
	exgcd(a, b, x, y);
	return x < 0 ? x + mo : x;
}

int a[maxn][maxn];

int matrix_tree(int n)
{
	int res = 1; n--;
	for (int i = 0; i < n; ++i) {
		for (int j = i; j < n; ++j) {
			if (a[j][i]) {
				if (i != j) {
					for (int k = i; k < n; ++k)	std::swap(a[i][k], a[j][k]);
					res = -res;
					if (res < 0) res += mo ;
				}
				break;
			}
		}
		if (!a[i][i]) return 0 ;
		for (int j = i + 1; j < n; ++j) {
			int mut = ((long long)a[j][i] * inv(a[i][i])) % mo;
			for (int k = i; k < n; ++k) {
				a[j][k] -= ((long long)a[i][k] * mut) % mo;
				if (a[j][k] < 0) a[j][k] += mo;
			}
		}
	}
	for (int i = 0; i < n; ++i)	res = ((long long)res * a[i][i]) % mo;
	return res % mo;
}

int id[maxn];

int calc()
{
	int inv2 = inv(2);
	for (int small = 0; small < n; small++) {
		std::memset(dp, 0, sizeof(dp));
		dp[small][1 << small] = 1;
		for (int state = 0; state < (1 << n); state++)
			for (int j = 0; j < n; j++)
				if (dp[j][state]) {
					for (int k = small + 1; k < n; k++)
						if (!((state >> k) & 1) && map[j][k])
							(dp[k][state | (1 << k)] += dp[j][state]) %= mo;
					if (map[j][small])
						(circle[state] += dp[j][state]) %= mo;
				}
	}

	for (int i = 0; i < n; i++)
		for (int j = i + 1; j < n; j++)
			if (map[i][j])
				(circle[(1 << i) | (1 << j)] += mo - 1) %= mo;
				//circle[(1 << i) | (1 << j)] = 0;
	for (int i = 0; i < (1 << n); i++)
		circle[i] = ((long long)circle[i] * (long long)inv2) % mo;

	int ans = 0;
	for (int i = 0; i < (1 << n); i++)
		if (circle[i]) {
			for (int j = 0; j < n; j++)
				for (int k = 0; k < n; k++)
					a[j][k] = 0;
			for (int j = 0; j < n; j++) id[j] = -1;
			for (int j = 0; j < n; j++)
				if ((i >> j) & 1) id[j] = 0;

			int now = 1;
			for (int j = 0; j < n; j++)
				if (id[j] == -1) id[j] = now++;
			for (int j = 0; j < n; j++)
				for (int k = 0; k < n; k++)
					if (map[j][k] && id[j] != id[k]) {
						a[id[j]][id[j]]++;
						a[id[j]][id[k]]--;
					}
			(ans += ((long long)circle[i] * matrix_tree(now)) % mo) %= mo;
		}
	return ans % mo;
}

int main()
{
	std::ios::sync_with_stdio(false);
	while (std::cin >> n >> m) {
		init();
		for (int i = 0, x, y; i < m; i++) {
			std::cin >> x >> y;
			x--; y--;
			map[x][y] = map[y][x] = true;
		}
		std::cout << calc() << '\n';
	}
}

